#if !defined(PROTONOS8_HPP)
#define PROTONOS8_HPP

#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/multi_array.hpp>

namespace protonos8 { 

#	ifdef _MSC_VER
#		define PACK( __Declaration__ ) __pragma( pack(push, 1) ) __Declaration__ __pragma( pack(pop) )
#	else
#		define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#	endif

enum { udp_max_size = 65516, udp_safe_max_size = 548, hex_max_size = (255*2 + 11) };

typedef enum {
	fid_info = 0,
	fid_load_blob = 255,
	fid_another_load_blob = 254,
	fid_raw_read = 10,
	fid_raw_write = 11,
	fid_init_m22 = 12, fid_read_m22 = 13,
	fid_init_m23 = 20, fid_read_m23 = 21,
	fid_init_m26 = 30, fid_write_m26 = 31,
	fid_init_m27 = 40, fid_write_m27 = 41,
	fid_init_m28 = 50, fid_read_m28 = 51,
	fid_init_m32 = 60, fid_read_m32 = 61,
	fid_init_m33 = 70, fid_read_m33 = 71,
	fid_init_m34 = 80, fid_read_m34 = 81,
	fid_init_mmc01 = 100, fid_write_mmc01 = 101,
	fid_init_mmc03 = 108, fid_rw_mmc03 = 109,
	fid_init_mmc06 = 116, fid_rw_mmc06 = 117,
} function_id;

typedef enum {
	nested_fid_start = 0x5a,
	nested_fid_start_magic = 0x0ca7,
	nested_fid_data = 0x01,
	nested_fid_write = 0xaa,
	nested_fid_write_magic = 0xa7ec,
} load_blob_nested_fid;

typedef enum {
	success = 0,
	no_answer = 1,
	fid_unsupported = 2,
	out_of_order = 3,
	wrong_ack = 15,
	piggyback_missed = 16,
} ack_result;

PACK(typedef struct {
	unsigned char counter : 5;
	unsigned char piggyback_location : 3;
}) pb_address;

PACK(typedef struct {
	uint8_t fid;
	pb_address addr;
}) req_hdr;

PACK(typedef struct {
	uint8_t fid;
	pb_address addr;
	uint8_t result;
}) ack_hdr;

PACK(typedef struct {
	ack_hdr hdr;
	uint32_t data;
}) pb_info_ack;

PACK(typedef struct {
	uint8_t fid;
	uint8_t nested_fid;
	uint16_t args;
}) pb_load_blob_req_hdr;

PACK(typedef struct {
	uint8_t fid;
	uint8_t nested_fid;
	uint8_t result;
	uint16_t next_line;
}) pb_load_blob_ack;

PACK(typedef struct {
	req_hdr hdr;
	uint32_t addr;
	uint32_t data;
}) pb_raw_rw_req;

PACK(typedef struct {
	ack_hdr hdr;
	uint32_t data;
}) pb_raw_rw_ack;

PACK(typedef struct {
	req_hdr hdr;
	uint8_t ini_set;
}) pb_init_location_req;

PACK(typedef struct {
	req_hdr hdr;
	uint16_t inp_mask;
}) pb_adc_req;

PACK(typedef struct {
	req_hdr hdr;
	uint8_t out_mask;
	uint16_t dout[4];
}) pb_dac_req;

PACK(typedef struct {
	req_hdr hdr;
	uint8_t inp_chan;
}) pb_counters_req;

PACK(typedef struct {
	req_hdr hdr;
	uint8_t out[8];
}) pb_write_mmc01_req;

PACK(typedef struct {
	req_hdr hdr;
	uint8_t out_mask;
	uint32_t inp_mask;
	uint16_t dout[8];
}) pb_mmc_rw_req;

class client {
public:
		
	client(const char* host, const char* port, boost::posix_time::time_duration timeout = boost::posix_time::seconds(1));

	ack_result info(uint32_t& data);

	ack_result load_blob(char* fn);
		
	ack_result raw_read(unsigned pb_loc, uint32_t addr, uint32_t& value)
		{ return raw_rw(fid_raw_read, pb_loc, addr, value); }
		
	ack_result raw_write(unsigned pb_loc, uint32_t addr, uint32_t& value)
		{ return raw_rw(fid_raw_write, pb_loc, addr, value); }

	ack_result init_m22(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m22, pb_loc, ini_set); }
		
	ack_result read_m22(unsigned pb_loc, uint16_t inp_mask, uint16_t* dinp)
		{ return read_adc(fid_read_m22, pb_loc, inp_mask, dinp); }
		
	ack_result init_m23(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m23, pb_loc, ini_set); }
		
	ack_result read_m23(unsigned pb_loc, uint16_t inp_mask, uint16_t* dinp)
		{ return read_adc(fid_read_m23, pb_loc, inp_mask, dinp); }
		
	ack_result init_m26(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m26, pb_loc, ini_set); }
		
	ack_result write_m26(unsigned pb_loc, uint8_t out_mask, uint16_t* dout)
		{ return write_dac(fid_write_m26, pb_loc, out_mask, dout); }
		
	ack_result init_m27(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m27, pb_loc, ini_set); }
		
	ack_result write_m27(unsigned pb_loc, uint8_t out_mask, uint16_t* dout)
		{ return write_dac(fid_write_m27, pb_loc, out_mask, dout); }
		
	ack_result init_m28(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m28, pb_loc, ini_set); }
		
	ack_result read_m28(unsigned pb_loc, uint8_t inp_chan, uint32_t* chn)
		{ return read_counters(fid_read_m28, pb_loc, inp_chan, chn); }
		
	ack_result init_m32(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m32, pb_loc, ini_set); }
		
	ack_result read_m32(unsigned pb_loc, uint8_t inp_chan, uint32_t* chn)
		{ return read_counters(fid_read_m32, pb_loc, inp_chan, chn); }
		
	ack_result init_m33(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m33, pb_loc, ini_set); }
		
	ack_result read_m33(unsigned pb_loc, uint16_t inp_mask, uint16_t* dinp)
		{ return read_adc(fid_read_m33, pb_loc, inp_mask, dinp); }
		
	ack_result init_m34(unsigned pb_loc, uint8_t ini_set)
		{ return init_location(fid_init_m34, pb_loc, ini_set); }
		
	ack_result read_m34(unsigned pb_loc, uint16_t inp_mask, uint16_t* dinp)
		{ return read_adc(fid_read_m34, pb_loc, inp_mask, dinp); }
		
	ack_result init_mmc01(uint8_t ini_set)
		{ return init_location(fid_init_mmc01, 0, ini_set); }
		
	ack_result write_mmc01(uint8_t* out);
		
	ack_result init_mmc03(uint8_t ini_set)
		{ return init_location(fid_init_mmc03, 0, ini_set); }
		
	ack_result rw_mmc03(uint8_t out_mask, uint16_t* dout,
		uint32_t inp_mask, uint16_t* dinp)
		{ return rw_mmc(fid_rw_mmc03, out_mask, dout, inp_mask, dinp); }
		
	ack_result init_mmc06(uint8_t ini_set)
		{ return init_location(fid_init_mmc06, 0, ini_set); }
		
	ack_result rw_mmc06(uint8_t out_mask, uint16_t* dout,
		uint32_t inp_mask, uint16_t* dinp)
		{ return rw_mmc(fid_rw_mmc06, out_mask, dout, inp_mask, dinp); }

private:
	ack_result raw_rw(function_id fid, unsigned pb_loc,
		uint32_t addr, uint32_t& value);
		
	ack_result rw_mmc(function_id fid,
		uint8_t out_mask, uint16_t* dout,
		uint32_t inp_mask, uint16_t* dinp);
		
	ack_result read_counters(function_id fid, unsigned pb_loc,
		uint8_t inp_chan, uint32_t* chn);
		
	ack_result read_adc(function_id fid, unsigned pb_loc,
		uint16_t inp_mask, uint16_t* dinp);
		
	ack_result write_dac(function_id fid, unsigned pb_loc,
		uint8_t out_mask, uint16_t* dout);
		
	ack_result init_location(function_id fid, unsigned pb_loc,
		uint8_t ini_set);

	ack_result load_blob_send_start_command(size_t& n);
	ack_result load_blob_send_data(size_t& n, std::string& s);
	ack_result load_blob_send_write_command(void);

	void check_deadline();
		
	std::size_t receive(const boost::asio::mutable_buffer& buffer,
		boost::posix_time::time_duration timeout,
	    boost::system::error_code& ec);
		
	static void handle_receive(
		const boost::system::error_code& ec, std::size_t length,
		boost::system::error_code* out_ec, std::size_t* out_length);

	boost::asio::io_service io_service_;
	boost::asio::ip::udp::socket socket_;
	boost::asio::ip::udp::resolver resolver_;
	boost::asio::ip::udp::resolver::query query_;
	boost::asio::ip::udp::resolver::iterator iterator_;
	boost::asio::deadline_timer deadline_;
	boost::posix_time::time_duration timeout_;
	boost::asio::ip::udp::endpoint sender_endpoint_;
	boost::multi_array<int,2> fid_counters;
	
	enum { max_length = protonos8::udp_safe_max_size };
};

}

#endif
